/**
 * Copyright (c) 2005 - 2010, James Auldridge
 * All rights reserved.
 *
 * Licensed under the BSD, MIT, and GPL (your choice!) Licenses:
 *  http://code.google.com/p/cookies/wiki/License
 *
 */
var jaaulde = window.jaaulde || {};
jaaulde.utils = jaaulde.utils || {};
jaaulde.utils.cookies = (function() {
  var resolveOptions,
    assembleOptionsString,
    parseCookies,
    constructor,
    defaultOptions = {
      expiresAt: null,
      path: '/',
      domain: null,
      secure: false,
    };
  /**
   * resolveOptions - receive an options object and ensure all options are present and valid, replacing with defaults where necessary
   *
   * @access private
   * @static
   * @parameter Object options - optional options to start with
   * @return Object complete and valid options object
   */
  resolveOptions = function(options) {
    var returnValue, expireDate;

    if (typeof options !== 'object' || options === null) {
      returnValue = defaultOptions;
    } else {
      returnValue = {
        expiresAt: defaultOptions.expiresAt,
        path: defaultOptions.path,
        domain: defaultOptions.domain,
        secure: defaultOptions.secure,
      };

      if (typeof options.expiresAt === 'object' && options.expiresAt instanceof Date) {
        returnValue.expiresAt = options.expiresAt;
      } else if (typeof options.hoursToLive === 'number' && options.hoursToLive !== 0) {
        expireDate = new Date();
        expireDate.setTime(expireDate.getTime() + options.hoursToLive * 60 * 60 * 1000);
        returnValue.expiresAt = expireDate;
      }

      if (typeof options.path === 'string' && options.path !== '') {
        returnValue.path = options.path;
      }

      if (typeof options.domain === 'string' && options.domain !== '') {
        returnValue.domain = options.domain;
      }

      if (options.secure === true) {
        returnValue.secure = options.secure;
      }
    }

    return returnValue;
  };
  /**
   * assembleOptionsString - analyze options and assemble appropriate string for setting a cookie with those options
   *
   * @access private
   * @static
   * @parameter options OBJECT - optional options to start with
   * @return STRING - complete and valid cookie setting options
   */
  assembleOptionsString = function(options) {
    options = resolveOptions(options);

    return (
      (typeof options.expiresAt === 'object' && options.expiresAt instanceof Date
        ? '; expires=' + options.expiresAt.toGMTString()
        : '') +
      '; path=' +
      options.path +
      (typeof options.domain === 'string' ? '; domain=' + options.domain : '') +
      (options.secure === true ? '; secure' : '')
    );
  };
  /**
   * parseCookies - retrieve document.cookie string and break it into a hash with values decoded and unserialized
   *
   * @access private
   * @static
   * @return OBJECT - hash of cookies from document.cookie
   */
  parseCookies = function() {
    var cookies = {},
      i,
      pair,
      name,
      value,
      separated = document.cookie.split(';'),
      unparsedValue;
    for (i = 0; i < separated.length; i = i + 1) {
      pair = separated[i].split('=');
      name = pair[0].replace(/^\s*/, '').replace(/\s*$/, '');

      try {
        value = decodeURIComponent(pair[1]);
      } catch (e1) {
        value = pair[1];
      }

      if (typeof JSON === 'object' && JSON !== null && typeof JSON.parse === 'function') {
        try {
          unparsedValue = value;
          value = JSON.parse(value);
        } catch (e2) {
          value = unparsedValue;
        }
      }

      cookies[name] = value;
    }
    return cookies;
  };

  constructor = function() {};

  /**
   * get - get one, several, or all cookies
   *
   * @access public
   * @paramater Mixed cookieName - String:name of single cookie; Array:list of multiple cookie names; Void (no param):if you want all cookies
   * @return Mixed - Value of cookie as set; Null:if only one cookie is requested and is not found; Object:hash of multiple or all cookies (if multiple or all requested);
   */
  constructor.prototype.get = function(cookieName) {
    var returnValue,
      item,
      cookies = parseCookies();

    if (typeof cookieName === 'string') {
      returnValue = typeof cookies[cookieName] !== 'undefined' ? cookies[cookieName] : null;
    } else if (typeof cookieName === 'object' && cookieName !== null) {
      returnValue = {};
      for (item in cookieName) {
        if (typeof cookies[cookieName[item]] !== 'undefined') {
          returnValue[cookieName[item]] = cookies[cookieName[item]];
        } else {
          returnValue[cookieName[item]] = null;
        }
      }
    } else {
      returnValue = cookies;
    }

    return returnValue;
  };
  /**
   * filter - get array of cookies whose names match the provided RegExp
   *
   * @access public
   * @paramater Object RegExp - The regular expression to match against cookie names
   * @return Mixed - Object:hash of cookies whose names match the RegExp
   */
  constructor.prototype.filter = function(cookieNameRegExp) {
    var cookieName,
      returnValue = {},
      cookies = parseCookies();

    if (typeof cookieNameRegExp === 'string') {
      cookieNameRegExp = new RegExp(cookieNameRegExp);
    }

    for (cookieName in cookies) {
      if (cookieName.match(cookieNameRegExp)) {
        returnValue[cookieName] = cookies[cookieName];
      }
    }

    return returnValue;
  };
  /**
   * set - set or delete a cookie with desired options
   *
   * @access public
   * @paramater String cookieName - name of cookie to set
   * @paramater Mixed value - Any JS value. If not a string, will be JSON encoded; NULL to delete
   * @paramater Object options - optional list of cookie options to specify
   * @return void
   */
  constructor.prototype.set = function(cookieName, value, options) {
    if (typeof options !== 'object' || options === null) {
      options = {};
    }

    if (typeof value === 'undefined' || value === null) {
      value = '';
      options.hoursToLive = -8760;
    } else if (typeof value !== 'string') {
      if (typeof JSON === 'object' && JSON !== null && typeof JSON.stringify === 'function') {
        value = JSON.stringify(value);
      } else {
        throw new Error('cookies.set() received non-string value and could not serialize.');
      }
    }

    var optionsString = assembleOptionsString(options);

    document.cookie = cookieName + '=' + encodeURIComponent(value) + optionsString;
  };
  /**
   * del - delete a cookie (domain and path options must match those with which the cookie was set; this is really an alias for set() with parameters simplified for this use)
   *
   * @access public
   * @paramater MIxed cookieName - String name of cookie to delete, or Bool true to delete all
   * @paramater Object options - optional list of cookie options to specify ( path, domain )
   * @return void
   */
  constructor.prototype.del = function(cookieName, options) {
    var allCookies = {},
      name;

    if (typeof options !== 'object' || options === null) {
      options = {};
    }

    if (typeof cookieName === 'boolean' && cookieName === true) {
      allCookies = this.get();
    } else if (typeof cookieName === 'string') {
      allCookies[cookieName] = true;
    }

    for (name in allCookies) {
      if (typeof name === 'string' && name !== '') {
        this.set(name, null, options);
      }
    }
  };
  /**
   * test - test whether the browser is accepting cookies
   *
   * @access public
   * @return Boolean
   */
  constructor.prototype.test = function() {
    var returnValue = false,
      testName = 'cT',
      testValue = 'data';

    this.set(testName, testValue);

    if (this.get(testName) === testValue) {
      this.del(testName);
      returnValue = true;
    }

    return returnValue;
  };
  /**
   * setOptions - set default options for calls to cookie methods
   *
   * @access public
   * @param Object options - list of cookie options to specify
   * @return void
   */
  constructor.prototype.setOptions = function(options) {
    if (typeof options !== 'object') {
      options = null;
    }

    defaultOptions = resolveOptions(options);
  };

  return new constructor();
})();

(function() {
  if (window.jQuery) {
    (function($) {
      $.cookies = jaaulde.utils.cookies;

      var extensions = {
        /**
         * $( 'selector' ).cookify - set the value of an input field, or the innerHTML of an element, to a cookie by the name or id of the field or element
         *                           (field or element MUST have name or id attribute)
         *
         * @access public
         * @param options OBJECT - list of cookie options to specify
         * @return jQuery
         */
        cookify: function(options) {
          return this.each(function() {
            var i,
              nameAttrs = ['name', 'id'],
              name,
              $this = $(this),
              value;

            for (i in nameAttrs) {
              if (!isNaN(i)) {
                name = $this.attr(nameAttrs[i]);
                if (typeof name === 'string' && name !== '') {
                  if ($this.is(':checkbox, :radio')) {
                    if ($this.attr('checked')) {
                      value = $this.val();
                    }
                  } else if ($this.is(':input')) {
                    value = $this.val();
                  } else {
                    value = $this.html();
                  }

                  if (typeof value !== 'string' || value === '') {
                    value = null;
                  }

                  $.cookies.set(name, value, options);

                  break;
                }
              }
            }
          });
        },
        /**
         * $( 'selector' ).cookieFill - set the value of an input field or the innerHTML of an element from a cookie by the name or id of the field or element
         *
         * @access public
         * @return jQuery
         */
        cookieFill: function() {
          return this.each(function() {
            var n,
              getN,
              nameAttrs = ['name', 'id'],
              name,
              $this = $(this),
              value;

            getN = function() {
              n = nameAttrs.pop();
              return !!n;
            };

            while (getN()) {
              name = $this.attr(n);
              if (typeof name === 'string' && name !== '') {
                value = $.cookies.get(name);
                if (value !== null) {
                  if ($this.is(':checkbox, :radio')) {
                    if ($this.val() === value) {
                      $this.attr('checked', 'checked');
                    } else {
                      $this.removeAttr('checked');
                    }
                  } else if ($this.is(':input')) {
                    $this.val(value);
                  } else {
                    $this.html(value);
                  }
                }

                break;
              }
            }
          });
        },
        /**
         * $( 'selector' ).cookieBind - call cookie fill on matching elements, and bind their change events to cookify()
         *
         * @access public
         * @param options OBJECT - list of cookie options to specify
         * @return jQuery
         */
        cookieBind: function(options) {
          return this.each(function() {
            var $this = $(this);
            $this.cookieFill().change(function() {
              $this.cookify(options);
            });
          });
        },
      };

      $.each(extensions, function(i) {
        $.fn[i] = this;
      });
    })(window.jQuery);
  }
})();
